﻿using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Criterion;
using NhJump.Persistence.Batching;
using NhJump.Persistence.Context;
using NhJump.Persistence.Model;

namespace NhJump.Persistence.Querying.Executable
{
    public class CriteriaQuery<T, TIdentifier> : IExecutableQuery<T, TIdentifier>
        where T : IDomainEntity<TIdentifier>
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly ICriteria _criteria;

        private ICriteria _countProjectionCriteria = null;
        private ICriteria CountProjectionCriteria
        {
            get {
                return _countProjectionCriteria ??
                       (_countProjectionCriteria = CriteriaTransformer.TransformToRowCount(_criteria));
            }
        }

        public CriteriaQuery(ICriteria criteria, IUnitOfWork unitOfWork)
        {
            _criteria = criteria;
            _unitOfWork = unitOfWork;
        }
        
        public IValue<T> SingleOrDefault()
        {
            return new DeferredValue<T>(_unitOfWork.Batcher, _criteria,
                            dbResult =>
                                {
                                    if (dbResult.Count == 0)
                                        return default(T);

                                    if (dbResult.Count > 1)
                                        throw new Exception("More than one result was found.  Not unique");

                                    return (T) dbResult[0];
                                });
        }

        public IValue<T> Unique()
        {
            return new DeferredValue<T>(_unitOfWork.Batcher, _criteria,
                            dbResult =>
                                {
                                    if (dbResult.Count == 0)
                                        throw new Exception("Unique result was not found");

                                    if (dbResult.Count > 1)
                                        throw new Exception("More than one result was found.  Not unique");

                                    return (T) dbResult[0];
                                });
        }

        public IEnumerable<T> Enumerate()
        {
            return new DeferredEnumeration<T>(_unitOfWork.Batcher, _criteria);
        }

        public IEnumerable<T> Page(int skip, int take)
        {
            return new DeferredEnumeration<T>(
                _unitOfWork.Batcher,
                CriteriaTransformer.Clone(_criteria).SetFirstResult(skip).SetMaxResults(take));
        }

        public IValue<bool> Exists()
        {
            return new DeferredValue<bool>(_unitOfWork.Batcher, 
                CountProjectionCriteria, 
                dbResult => Convert.ToInt32(dbResult[0]) > 0);
        }

        public IValue<int> Count()
        {
            return new DeferredValue<int>(
                _unitOfWork.Batcher, 
                CountProjectionCriteria, 
                dbResult => Convert.ToInt32(dbResult[0]));
        }
    }
}